#include "Shader.h"

using namespace GalliumEngine;

Shader::Shader(void)
{
}

Shader::Shader(std::string _id)
{
	id = _id;
}

Shader::~Shader(void)
{
		
}

std::string Shader::getId()
{
	return id;
}

void Shader::initialize(std::string vertexShader, std::string geometryShader, std::string fragmentShader, std::vector<BindParams> params)
{
	Logger::getInstance()->write("Initializing Shader Program:");
	Logger::getInstance()->write(StringUtils::format("Vertex Shader: %s",vertexShader.c_str()));
	Logger::getInstance()->write(StringUtils::format("Fragment Shader: %s",fragmentShader.c_str()));

	program = glCreateProgram();

	if(vertexShader != "")
	{
		loadShader(vertexShader,GL_VERTEX_SHADER);
	}

	if(geometryShader != "")
	{
		loadShader(geometryShader,GL_GEOMETRY_SHADER);
	}

	if(fragmentShader != "")
	{
		loadShader(fragmentShader,GL_FRAGMENT_SHADER);
	}

	linkProgram();

	bind(params);

	Logger::getInstance()->write("Initialized Shader Program");
}

void Shader::deinitialize()
{
	glDeleteProgram(program);
}

char* Shader::loadShaderFile(const char* filename)
{
	FILE* input;
	fopen_s(&input, filename, "rb");

	if(input == NULL) {return NULL; }
	if(fseek(input,0,SEEK_END) == -1) { return NULL; }

	long size = ftell(input);

	if(size == -1) { return NULL; }
	if(fseek(input,0,SEEK_SET) == -1) { return NULL; }
    
	char *content = (char*)malloc((size_t)size+1);

	if(content == NULL) { return NULL; }

	fread(content,1,(size_t)size,input);
	if(ferror(input))
	{
		free(content);
		return NULL;
	}

	fclose(input);
	content[size]='\0';
	return content;
}

void Shader::loadShader(std::string shaderFile, GLenum type)
{
	GLchar* source;

	source = loadShaderFile(shaderFile.c_str());

	if (source == NULL) 
	{
		Logger::getInstance()->write(StringUtils::format("Failed to read the shader: %s",shaderFile.c_str()));
		exit(EXIT_FAILURE);
	}

	GLuint shader = glCreateShader(type);
	glShaderSource(shader, 1, (const char **)&source, NULL);
	glCompileShader(shader);

	GLint compiled;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);

	GLint logSize;
	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logSize);

	char* logMsg = new char[logSize];
	glGetShaderInfoLog(shader, logSize, NULL, logMsg);

	if (!compiled) 
	{
		Logger::getInstance()->write(StringUtils::format("Failed to compile the shader: %s",shaderFile.c_str()));
		Logger::getInstance()->write(StringUtils::format("Error Message: %s",logMsg));
		exit(EXIT_FAILURE);
	}
	else
	{
		if(logSize > 1)
		{
			Logger::getInstance()->write(StringUtils::format("Non Critical Issues while Compiling: %s",shaderFile.c_str()));
			Logger::getInstance()->write(StringUtils::format("Message: %s",logMsg));
		}
	}

	delete [] logMsg;

	glAttachShader(program, shader);

	free(source);
}

void Shader::linkProgram()
{
	glLinkProgram(program);

	GLint linked;
	glGetProgramiv(program, GL_LINK_STATUS, &linked);

	GLint logSize;
	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logSize);
	

	char* logMsg = new char[logSize];
	glGetProgramInfoLog(program, logSize, NULL, logMsg);

	if (!linked) 
	{
		Logger::getInstance()->write(StringUtils::format("Failed to link the shader program"));
		Logger::getInstance()->write(StringUtils::format("Error Message: %s",logMsg));

		exit(EXIT_FAILURE);
	}
	else
	{
		if(logSize > 1)
		{
			Logger::getInstance()->write(StringUtils::format("Non Critical Issues while Linking"));
			Logger::getInstance()->write(StringUtils::format("Message: %s",logMsg));
		}
	}

	delete [] logMsg;

}

void Shader::bind(std::vector<BindParams> params)
{
	for(unsigned int i = 0; i < params.size(); ++i)
	{
		if(params[i].type == BINDTYPE_ATTRIBUTE)
		{
			GLuint attributeValue = glGetAttribLocation(program, params[i].id.c_str());
			boundValues.insert(boundValues_pair(params[i].id,attributeValue));
			if (attributeValue == -1) 
			{
				Logger::getInstance()->write(StringUtils::format("Failed to bind attribute %s",params[i].id.c_str()));
			}
			else
			{
				Logger::getInstance()->write(StringUtils::format("Bound attribute %s to %u",params[i].id.c_str(),attributeValue));
			}
		}
		else if(params[i].type == BINDTYPE_UNIFORM)
		{
			GLuint uniformValue = glGetUniformLocation(program, params[i].id.c_str());
			boundValues.insert(boundValues_pair(params[i].id,uniformValue));
			if (uniformValue == -1) 
			{
				Logger::getInstance()->write(StringUtils::format("Failed to bind uniform %s",params[i].id.c_str()));
			}
			else
			{
				Logger::getInstance()->write(StringUtils::format("Bound uniform %s to %u",params[i].id.c_str(),uniformValue));
			}
		}
		else
		{
		}
	}
}
		
void Shader::begin()
{
	glUseProgram(program);
}

void Shader::end()
{
	glUseProgram(0);
}